{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Deep Research\n",
    "\n",
    "One of the classic cross-business Agentic use cases! This is huge."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<table style=\"margin: 0; text-align: left; width:100%\">\n",
    "    <tr>\n",
    "        <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
    "            <img src=\"../assets/business.png\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
    "        </td>\n",
    "        <td>\n",
    "            <h2 style=\"color:#00bfff;\">Commercial implications</h2>\n",
    "            <span style=\"color:#00bfff;\">A Deep Research agent is broadly applicable to any business area, and to your own day-to-day activities. You can make use of this yourself!\n",
    "            </span>\n",
    "        </td>\n",
    "    </tr>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "from agents import Agent, WebSearchTool, trace, Runner, gen_trace_id, function_tool\n",
    "from agents.model_settings import ModelSettings\n",
    "from pydantic import BaseModel, Field\n",
    "from dotenv import load_dotenv\n",
    "import asyncio\n",
    "import requests\n",
    "import os\n",
    "from typing import Dict\n",
    "from IPython.display import display, Markdown"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## OpenAI Hosted Tools\n",
    "\n",
    "OpenAI Agents SDK includes the following hosted tools:\n",
    "\n",
    "The `WebSearchTool` lets an agent search the web.  \n",
    "The `FileSearchTool` allows retrieving information from your OpenAI Vector Stores.  \n",
    "The `ComputerTool` allows automating computer use tasks like taking screenshots and clicking.\n",
    "\n",
    "### Important note - API charge of WebSearchTool\n",
    "\n",
    "This is costing me 2.5 cents per call for OpenAI WebSearchTool. That can add up to $2-$3 for the next 2 labs. We'll use free and low cost Search tools with other platforms, so feel free to skip running this if the cost is a concern. Also student Christian W. pointed out that OpenAI can sometimes charge for multiple searches for a single call, so it could sometimes cost more than 2.5 cents per call.\n",
    "\n",
    "Costs are here: https://platform.openai.com/docs/pricing#web-search"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "INSTRUCTIONS = \"You are a research assistant. Given a search term, you search the web for that term and \\\n",
    "produce a concise summary of the results. The summary must 2-3 paragraphs and less than 300 \\\n",
    "words. Capture the main points. Write succintly, no need to have complete sentences or good \\\n",
    "grammar. This will be consumed by someone synthesizing a report, so it's vital you capture the \\\n",
    "essence and ignore any fluff. Do not include any additional commentary other than the summary itself.\"\n",
    "\n",
    "search_agent = Agent(\n",
    "    name=\"Search agent\",\n",
    "    instructions=INSTRUCTIONS,\n",
    "    tools=[WebSearchTool(search_context_size=\"low\")],\n",
    "    model=\"gpt-4o-mini\",\n",
    "    model_settings=ModelSettings(tool_choice=\"required\"),\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "In 2025, several AI agent frameworks have emerged, each offering unique capabilities for developing intelligent, autonomous systems. LangChain stands out as a modular framework for building applications powered by large language models (LLMs). It provides tools for chaining prompts, models, memory, and external tools, facilitating the creation of complex workflows. LangGraph extends LangChain by introducing a graph-based approach to define agent workflows, offering explicit control over information flow and enabling branching and debugging of complex behaviors. CrewAI focuses on role-based agent collaboration, allowing the creation of specialized agents that work together on complex projects, akin to a team environment. AutoGen, developed by Microsoft, specializes in orchestrating multiple AI agents to form autonomous, event-driven systems capable of handling complex, multi-agent tasks seamlessly. Eliza is an open-source, Web3-friendly agentic framework that integrates seamlessly with blockchain applications, enabling the deployment of decentralized AI agents. Agent Lightning is a flexible and extensible framework that enables reinforcement learning-based training of LLMs for any AI agent, allowing seamless integration with existing agents developed via diverse methods. AutoAgent is a fully-automated, zero-code framework that enables users to create and deploy LLM agents through natural language alone, serving as a versatile multi-agent system for general AI assistants. These frameworks represent the forefront of AI agent development, each contributing to the advancement of intelligent, autonomous systems in 2025. "
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "message = \"Latest AI Agent frameworks in 2025\"\n",
    "\n",
    "with trace(\"Search\"):\n",
    "    result = await Runner.run(search_agent, message)\n",
    "\n",
    "display(Markdown(result.final_output))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### As always, take a look at the trace\n",
    "\n",
    "https://platform.openai.com/traces"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### We will now use Structured Outputs, and include a description of the fields"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# See note above about cost of WebSearchTool\n",
    "\n",
    "HOW_MANY_SEARCHES = 3\n",
    "\n",
    "INSTRUCTIONS = f\"You are a helpful research assistant. Given a query, come up with a set of web searches \\\n",
    "to perform to best answer the query. Output {HOW_MANY_SEARCHES} terms to query for.\"\n",
    "\n",
    "# Use Pydantic to define the Schema of our response - this is known as \"Structured Outputs\"\n",
    "# With massive thanks to student Wes C. for discovering and fixing a nasty bug with this!\n",
    "\n",
    "class WebSearchItem(BaseModel):\n",
    "    reason: str = Field(description=\"Your reasoning for why this search is important to the query.\")\n",
    "\n",
    "    query: str = Field(description=\"The search term to use for the web search.\")\n",
    "\n",
    "\n",
    "class WebSearchPlan(BaseModel):\n",
    "    searches: list[WebSearchItem] = Field(description=\"A list of web searches to perform to best answer the query.\")\n",
    "\n",
    "\n",
    "planner_agent = Agent(\n",
    "    name=\"PlannerAgent\",\n",
    "    instructions=INSTRUCTIONS,\n",
    "    model=\"gpt-4o-mini\",\n",
    "    output_type=WebSearchPlan,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "searches=[WebSearchItem(reason='To gather information on the latest AI agent frameworks currently available or being developed.', query='latest AI agent frameworks 2025'), WebSearchItem(reason='To find comparisons and reviews of emerging AI frameworks in 2025.', query='best AI frameworks for agents 2025'), WebSearchItem(reason='To explore advancements and innovations in AI agent technology expected or realized in 2025.', query='AI agent technology trends 2025')]\n"
     ]
    }
   ],
   "source": [
    "\n",
    "message = \"Latest AI Agent frameworks in 2025\"\n",
    "\n",
    "with trace(\"Search\"):\n",
    "    result = await Runner.run(planner_agent, message)\n",
    "    print(result.final_output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "@function_tool\n",
    "def send_research_notification(report_content: str) -> Dict[str, str]:\n",
    "    \"\"\" Send a research report as a Pushover notification \"\"\"\n",
    "    url = \"https://api.pushover.net/1/messages.json\"\n",
    "    \n",
    "    # Clean up markdown for better readability in notifications\n",
    "    import re\n",
    "    clean_content = re.sub(r'#{1,6}\\s*', '', report_content)  # Remove markdown headers\n",
    "    clean_content = re.sub(r'\\*\\*(.*?)\\*\\*', r'\\1', clean_content)  # Remove bold markdown\n",
    "    clean_content = re.sub(r'\\*(.*?)\\*', r'\\1', clean_content)  # Remove italic markdown\n",
    "    clean_content = re.sub(r'\\n\\n+', '\\n\\n', clean_content)  # Clean multiple newlines\n",
    "    \n",
    "    # Truncate if too long for Pushover (max 1024 chars)\n",
    "    if len(clean_content) > 900:\n",
    "        clean_content = clean_content[:900] + \"...\\n\\n[Report truncated - see full version in trace]\"\n",
    "    \n",
    "    data = {\n",
    "        \"token\": os.environ.get('PUSHOVER_TOKEN'),\n",
    "        \"user\": os.environ.get('PUSHOVER_USER'),\n",
    "        \"message\": clean_content,\n",
    "        \"title\": \"Research Report Complete\"\n",
    "    }\n",
    "    \n",
    "    response = requests.post(url, data=data)\n",
    "    if response.status_code == 200:\n",
    "        return {\"status\": \"success\", \"message\": \"Research report notification sent successfully\"}\n",
    "    else:\n",
    "        return {\"status\": \"error\", \"message\": f\"Error {response.status_code}: {response.text}\"}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "FunctionTool(name='send_research_notification', description='Send a research report as a Pushover notification', params_json_schema={'properties': {'report_content': {'title': 'Report Content', 'type': 'string'}}, 'required': ['report_content'], 'title': 'send_research_notification_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=<function function_tool.<locals>._create_function_tool.<locals>._on_invoke_tool at 0x10bb1bb00>, strict_json_schema=True, is_enabled=True)"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "send_research_notification"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "INSTRUCTIONS = \"\"\"You are able to send a research report notification via Pushover.\n",
    "You will be provided with a detailed markdown report. You should use your tool to send one notification \n",
    "with the report content. The notification should be clean and readable.\"\"\"\n",
    "\n",
    "notification_agent = Agent(\n",
    "    name=\"Notification agent\",\n",
    "    instructions=INSTRUCTIONS,\n",
    "    tools=[send_research_notification],\n",
    "    model=\"gpt-4o-mini\",\n",
    ")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "INSTRUCTIONS = (\n",
    "    \"You are a senior researcher tasked with writing a cohesive report for a research query. \"\n",
    "    \"You will be provided with the original query, and some initial research done by a research assistant.\\n\"\n",
    "    \"You should first come up with an outline for the report that describes the structure and \"\n",
    "    \"flow of the report. Then, generate the report and return that as your final output.\\n\"\n",
    "    \"The final output should be in markdown format, and it should be lengthy and detailed. Aim \"\n",
    "    \"for 5-10 pages of content, at least 1000 words.\"\n",
    ")\n",
    "\n",
    "\n",
    "class ReportData(BaseModel):\n",
    "    short_summary: str = Field(description=\"A short 2-3 sentence summary of the findings.\")\n",
    "\n",
    "    markdown_report: str = Field(description=\"The final report\")\n",
    "\n",
    "    follow_up_questions: list[str] = Field(description=\"Suggested topics to research further\")\n",
    "\n",
    "\n",
    "writer_agent = Agent(\n",
    "    name=\"WriterAgent\",\n",
    "    instructions=INSTRUCTIONS,\n",
    "    model=\"gpt-4o-mini\",\n",
    "    output_type=ReportData,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The next 3 functions will plan and execute the search, using planner_agent and search_agent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "async def plan_searches(query: str):\n",
    "    \"\"\" Use the planner_agent to plan which searches to run for the query \"\"\"\n",
    "    print(\"Planning searches...\")\n",
    "    result = await Runner.run(planner_agent, f\"Query: {query}\")\n",
    "    print(f\"Will perform {len(result.final_output.searches)} searches\")\n",
    "    return result.final_output\n",
    "\n",
    "async def perform_searches(search_plan: WebSearchPlan):\n",
    "    \"\"\" Call search() for each item in the search plan \"\"\"\n",
    "    print(\"Searching...\")\n",
    "    tasks = [asyncio.create_task(search(item)) for item in search_plan.searches]\n",
    "    results = await asyncio.gather(*tasks)\n",
    "    print(\"Finished searching\")\n",
    "    return results\n",
    "\n",
    "async def search(item: WebSearchItem):\n",
    "    \"\"\" Use the search agent to run a web search for each item in the search plan \"\"\"\n",
    "    input = f\"Search term: {item.query}\\nReason for searching: {item.reason}\"\n",
    "    result = await Runner.run(search_agent, input)\n",
    "    return result.final_output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The next 2 functions write a report and send notification"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "async def write_report(query: str, search_results: list[str]):\n",
    "    \"\"\" Use the writer agent to write a report based on the search results\"\"\"\n",
    "    print(\"Thinking about report...\")\n",
    "    input = f\"Original query: {query}\\nSummarized search results: {search_results}\"\n",
    "    result = await Runner.run(writer_agent, input)\n",
    "    print(\"Finished writing report\")\n",
    "    return result.final_output\n",
    "\n",
    "async def send_notification(report: ReportData):\n",
    "    \"\"\" Use the notification agent to send a notification with the report \"\"\"\n",
    "    print(\"Sending notification...\")\n",
    "    result = await Runner.run(notification_agent, report.markdown_report)\n",
    "    print(\"Notification sent\")\n",
    "    return report"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Showtime!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Starting research...\n",
      "Planning searches...\n",
      "Will perform 3 searches\n",
      "Searching...\n",
      "Finished searching\n",
      "Thinking about report...\n",
      "Finished writing report\n",
      "Sending notification...\n",
      "Notification sent\n",
      "Hooray!\n"
     ]
    }
   ],
   "source": [
    "query =\"Latest AI Agent frameworks in 2025\"\n",
    "\n",
    "with trace(\"Research trace\"):\n",
    "    print(\"Starting research...\")\n",
    "    search_plan = await plan_searches(query)\n",
    "    search_results = await perform_searches(search_plan)\n",
    "    report = await write_report(query, search_results)\n",
    "    await send_notification(report)  \n",
    "    print(\"Hooray!\")\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### As always, take a look at the trace\n",
    "\n",
    "https://platform.openai.com/traces"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<table style=\"margin: 0; text-align: left; width:100%\">\n",
    "    <tr>\n",
    "        <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
    "            <img src=\"../assets/thanks.png\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
    "        </td>\n",
    "        <td>\n",
    "            <h2 style=\"color:#00cc00;\">Congratulations on your progress, and a request</h2>\n",
    "            <span style=\"color:#00cc00;\">You've reached an important moment with the course; you've created a valuable Agent using one of the latest Agent frameworks. You've upskilled, and unlocked new commercial possibilities. Take a moment to celebrate your success!<br/><br/>Something I should ask you -- my editor would smack me if I didn't mention this. If you're able to rate the course on Udemy, I'd be seriously grateful: it's the most important way that Udemy decides whether to show the course to others and it makes a massive difference.<br/><br/>And another reminder to <a href=\"https://www.linkedin.com/in/eddonner/\">connect with me on LinkedIn</a> if you wish! If you wanted to post about your progress on the course, please tag me and I'll weigh in to increase your exposure.\n",
    "            </span>\n",
    "        </td>\n",
    "    </tr>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
